package org.alerve.utils;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 
 * Common place to lazily organize the caches used in the application.
 * 
 * @author pablo.gonzalez@11870.com
 * 
 */
public class Cache<Key, Type> extends AbstractMap<Key, Type> {

	private ReferenceQueue<Type> referenceQueue = new ReferenceQueue<Type>();

	private Map<SoftReference<Type>, Key> keyMap = new HashMap<SoftReference<Type>, Key>();

	private Set<Entry<Key, Type>> entrySet;
	private List<Entry<Key, Type>> entryList;

	private synchronized void purge() {
		Reference<? extends Type> reference = null;
		while ((reference = referenceQueue.poll()) != null) {
			Key key = keyMap.remove(reference);
			remove(key);
		}
	}

	private class CacheEntry implements Entry<Key, Type> {

		private Key key;
		private SoftReference<Type> reference;

		CacheEntry(Key key, Type value) {
			this.key = key;
			setValue(value);
		}

		@Override
		public Key getKey() {
			return key;
		}

		@Override
		public Type getValue() {
			return reference.get();
		}

		@Override
		public Type setValue(Type object) {
			Type old = null;
			if (reference != null) {
				old = reference.get();
			}
			keyMap.remove(reference);
			reference = new SoftReference<Type>(object,
					(ReferenceQueue<? super Type>) referenceQueue);
			keyMap.put(reference, key);
			return old;
		}

		@SuppressWarnings("unchecked")
		@Override
		public boolean equals(Object o) {
			if (!(o instanceof Entry<?, ?>)) {
				return false;
			}
			Entry<Key, Type> e = (Entry<Key, Type>) o;
			Object value = reference.get();
			return (key == null ? e.getKey() == null : key.equals(e.getKey()))
					&& (value == null ? e.getValue() == null : value.equals(e
							.getValue()));
		}

		public int hashCode() {
			int keyHash = (key == null ? 0 : key.hashCode());
			Object value = reference.get();
			int valueHash = (value == null ? 0 : value.hashCode());
			return keyHash ^ valueHash;
		}

	}

	@Override
	public Set<Entry<Key, Type>> entrySet() {
		if(entryList == null) {
			entryList = new ArrayList<Entry<Key, Type>>();
		}
//		Reference<? extends Type> reference = null;
//		while ((reference = referenceQueue.poll()) != null) {
//			Key key = keyMap.remove(reference);
//			entryList.remove(key);
//		}
		if (entrySet == null) {
			entrySet = new AbstractSet<Entry<Key, Type>>() {

				@Override
				public void clear() {
					entryList.clear();
				}

				@Override
				public Iterator<Entry<Key, Type>> iterator() {
					return entryList.iterator();
				}

				@Override
				public int size() {
					return entryList.size();
				}

			};
		}
		return entrySet;
	}

	@Override
	public Type put(Key key, Type value) {
		if (value == null) {
			return null;
		}
		
		if(entryList == null) {
			entryList = new ArrayList<Entry<Key, Type>>();
		}
		int size = entryList.size();
		CacheEntry entry = null;
		int i;
		if (key == null) {
			for (i = 0; i < size; i++) {
				entry = (CacheEntry) (entryList.get(i));
				if (entry.getKey() == null) {
					break;
				}
			}
		} else {
			for (i = 0; i < size; i++) {
				entry = (CacheEntry) (entryList.get(i));
				if (key.equals(entry.getKey())) {
					break;
				}
			}
		}
		Type oldValue = null;
		if (i < size) {
			oldValue = entry.getValue();
			entry.setValue(value);
		} else {
			entryList.add(new CacheEntry(key, value));
		}
		return oldValue;
	}

	@Override
	public Type get(Object key) {
		purge();
		return super.get(key);
	}

	@Override
	public boolean containsKey(Object key) {
		purge();
		return super.containsKey(key);
	}
	
	
}
